﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.IO;
using System.Windows.Media.Imaging;

using System.Net;
using System.Windows;
using System.Runtime.Serialization;
using System.IO.IsolatedStorage;
using System.Windows.Media;



namespace BootyShaker.Serialization
{
    public class CustomBinarySerializer
    {

        private List<PropertyInfo> serializableProperties = new List<PropertyInfo>();
        private Type serializableObjectType;

        public CustomBinarySerializer(Type objectType)
        {
            serializableObjectType = objectType;
            serializableProperties = GetMarkedProperties(objectType);
        }

        private List<PropertyInfo> GetMarkedProperties(Type type)
        {
            return (from property in type.GetProperties()
                    where property.GetCustomAttributes(true)
                    .Where((x) => x is System.Runtime.Serialization.DataMemberAttribute).Count() > 0
                    select property
                    ).ToList();
        }

        #region Write

        public void WriteObject(Stream stream, object graph)
        {
            if (stream == null || graph == null)
                return;

            BinaryWriter bw = new BinaryWriter(stream);

            WriteObject(bw, graph);
        }

        public void WriteObject(BinaryWriter bw, object graph)
        {
            if (graph.GetType() == typeof(Shaker))
            {
                Version v = new AssemblyName(Assembly.GetExecutingAssembly().FullName).Version;

                bw.Write(v.Major);
                bw.Write(v.Minor);
                bw.Write(v.Build);

                PropertyInfo p = serializableObjectType.GetProperty("thumbnail");
                if (p != null)
                {
                    WriteBitmap(bw, p.GetValue(graph, null) as WriteableBitmap);
                }

                p = serializableObjectType.GetProperty("DampingRatio");
                if (p != null)
                {
                    bw.Write((double)p.GetValue(graph, null));
                }
                p = serializableObjectType.GetProperty("Frequency");
                if (p != null)
                {
                    bw.Write((double)p.GetValue(graph, null));
                }
                p = serializableObjectType.GetProperty("GravityRatio");
                if (p != null)
                {
                    bw.Write((double)p.GetValue(graph, null));
                }
                p = serializableObjectType.GetProperty("DragStrength");
                if (p != null)
                {
                    bw.Write((double)p.GetValue(graph, null));
                }
                p = serializableObjectType.GetProperty("MergeOverlay");
                if (p != null)
                {
                    bw.Write((bool)p.GetValue(graph, null));
                }
            }

            foreach (PropertyInfo pi in serializableProperties)
            {
                var value = pi.GetValue(graph, null);

                if (pi.PropertyType == typeof(string))
                {
                    bw.Write(value as string ?? string.Empty);
                }
                else if (pi.PropertyType == typeof(List<int>))
                {
                    WriteIntegerList(bw, value as List<int>);
                }
                else if (pi.PropertyType == typeof(double))
                {
                    bw.Write((double)value);
                }
                else if (pi.PropertyType == typeof(System.Windows.Point))
                {
                    bw.Write(((System.Windows.Point)value).X);
                    bw.Write(((System.Windows.Point)value).Y);
                }
                else if (pi.PropertyType == typeof(WriteableBitmap))
                {
                    WriteBitmap(bw, value as WriteableBitmap);
                }
                else if (pi.PropertyType == typeof(List<ShakeObject>))
                {
                    WriteShakeObjects(bw, value as List<ShakeObject>);
                }
                else if (pi.PropertyType == typeof(Matrix))
                {
                    WriteMatrix(bw, (Matrix)value);
                }
                else if (pi.PropertyType == typeof(System.Windows.Size))
                {
                    bw.Write(((System.Windows.Size)value).Width);
                    bw.Write(((System.Windows.Size)value).Height);
                }
                else throw new Exception("undefined serialization");
            }
        }

        private void WriteShakeObjects(BinaryWriter bw, List<ShakeObject> list)
        {
            if (list == null || !list.Any())
            {
                bw.Write(0);
            }
            else
            {
                bw.Write(list.Count);
                list.ForEach(x => BinarySerializationHelper.Serialize(bw, x));
            }
        }
        private void WriteIntegerList(BinaryWriter bw, List<int> list)
        {
            if (list == null || !list.Any())
            {
                bw.Write(0);
            }
            else
            {
                bw.Write(list.Count);
                list.ForEach(x => bw.Write(x));
            }
        }
        private void WriteBitmap(BinaryWriter bw, WriteableBitmap bmp)
        {
            bw.Write(bmp.PixelWidth);
            bw.Write(bmp.PixelHeight);
            //byte[] bytes = 
            Compress(bmp, bw);
            //bw.Write(bytes.Length);
            // compressedFile.CopyTo(bw.BaseStream);
            //bw.Write(bytes);

            //bmp.Pixels.ToList().ForEach(x => bw.Write(x));

        }
        private void WriteMatrix(BinaryWriter bw, Matrix matrix)
        {
            bw.Write(matrix.IsIdentity);
            bw.Write(matrix.M11);
            bw.Write(matrix.M12);
            bw.Write(matrix.M21);
            bw.Write(matrix.M22);
            bw.Write(matrix.OffsetX);
            bw.Write(matrix.OffsetY);
        }
        public void Compress(WriteableBitmap wb, BinaryWriter bw)
        {
            // Create a file name for the JPEG file in isolated storage.
            String tempJPEG = "TempJPEG";

            // Create a virtual store and file stream. Check for duplicate tempJPEG files.
            var myStore = IsolatedStorageFile.GetUserStoreForApplication();
            if (myStore.FileExists(tempJPEG))
            {
                myStore.DeleteFile(tempJPEG);
            }

            IsolatedStorageFileStream myFileStream = myStore.CreateFile(tempJPEG);

            // Encode the WriteableBitmap object to a JPEG stream.
            wb.SaveJpeg(myFileStream, wb.PixelWidth, wb.PixelHeight, 0, 85);
            myFileStream.Close();


            // Create a new stream from isolated storage, and save the JPEG file to the media library on Windows Phone.
            myFileStream = myStore.OpenFile(tempJPEG, FileMode.Open, FileAccess.Read);
            //BinaryReader br = new BinaryReader(myFileStream);
            bw.Write(myFileStream.Length);

            byte[] bytes = new byte[4096];
            int numBytesToRead = (int)myFileStream.Length;
            int numBytesRead = 0;
            while (numBytesToRead > 0)
            {
                // Read may return anything from 0 to 10.
                int n = myFileStream.Read(bytes, 0, numBytesToRead < bytes.Length ? numBytesToRead : bytes.Length);
                bw.Write(bytes, 0, n);
                // The end of the file is reached.
                if (n == 0)
                {
                    break;
                }
                numBytesRead += n;
                numBytesToRead -= n;
            }

            //while (myFileStream.Position < myFileStream.Length)
            //{
            //    byte inputChar = (byte)myFileStream.ReadByte();
            //    bw.Write(inputChar);
            //}

            // Determine the size of the IsolatedStorageFileStream
            // by checking its Length property.
            // br.ReadBytes((int)myFileStream.Length);

            //byte[] bytes = new byte[myFileStream.Length];
            //int numBytesToRead = (int)myFileStream.Length;
            //int numBytesRead = 0;
            //while (numBytesToRead > 0)
            //{
            //    // Read may return anything from 0 to 10.
            //    int n = myFileStream.Read(bytes, numBytesRead, numBytesToRead < 1000 ? numBytesToRead : 1000);
            //    // The end of the file is reached.
            //    if (n == 0)
            //    {
            //        break;
            //    }
            //    numBytesRead += n;
            //    numBytesToRead -= n;
            //}
            myFileStream.Close();

            //return bytes;
        }


        #endregion Write

        #region Read

        public object ReadObject(Stream stream)
        {
            if (stream == null)
                return null;

            BinaryReader br = new BinaryReader(stream);

            return ReadObject(br);
        }

        public object ReadObject(BinaryReader br)
        {


            object deserializedObject = Activator.CreateInstance(serializableObjectType);
            PropertyInfo p;
            if (serializableObjectType == typeof(Shaker))
            {
                try
                {
                    Version v = new Version(br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
                    p = serializableObjectType.GetProperty("version");
                    if (p != null)
                    {
                        p.SetValue(deserializedObject, v, null);
                    }


                }
                catch { }

                p = serializableObjectType.GetProperty("thumbnail");
                if (p != null)
                {
                    p.SetValue(deserializedObject, ReadBitmap(br), null);
                }

                //DampingRatio
                //Frequency
                //GravityRatio
                //DragStrength
                //MergeOverlay 
                p = serializableObjectType.GetProperty("DampingRatio");
                if (p != null)
                {
                    p.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                p = serializableObjectType.GetProperty("Frequency");
                if (p != null)
                {
                    p.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                p = serializableObjectType.GetProperty("GravityRatio");
                if (p != null)
                {
                    p.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                p = serializableObjectType.GetProperty("DragStrength");
                if (p != null)
                {
                    p.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                p = serializableObjectType.GetProperty("MergeOverlay");
                if (p != null)
                {
                    p.SetValue(deserializedObject, br.ReadBoolean(), null);
                }
            }
            foreach (PropertyInfo pi in serializableProperties)
            {
                if (pi.PropertyType == typeof(string))
                {
                    pi.SetValue(deserializedObject, br.ReadString(), null);
                }
                else if (pi.PropertyType == typeof(List<int>))
                {
                    pi.SetValue(deserializedObject, ReadIntegerList(br), null);
                }
                else if (pi.PropertyType == typeof(double))
                {
                    pi.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                else if (pi.PropertyType == typeof(System.Windows.Point))
                {
                    double X = br.ReadDouble();
                    double Y = br.ReadDouble();
                    pi.SetValue(deserializedObject, new System.Windows.Point(X, Y), null);

                }
                else if (pi.PropertyType == typeof(WriteableBitmap))
                {
                    pi.SetValue(deserializedObject, ReadBitmap(br), null);

                }
                else if (pi.PropertyType == typeof(List<ShakeObject>))
                {
                    pi.SetValue(deserializedObject, ReadShakeObjects(br), null);

                }
                else if (pi.PropertyType == typeof(System.Windows.Media.Matrix))
                {
                    pi.SetValue(deserializedObject, ReadMatrix(br), null);

                }
                else if (pi.PropertyType == typeof(System.Windows.Size))
                {
                    double wdt = br.ReadDouble();
                    double hgt = br.ReadDouble();
                    pi.SetValue(deserializedObject, new System.Windows.Size(wdt, hgt), null);

                }
                else throw new Exception("undefined deserialization");


            }

            return deserializedObject;
        }

        private List<ShakeObject> ReadShakeObjects(BinaryReader br)
        {
            List<ShakeObject> list = new List<ShakeObject>();
            int count = br.ReadInt32();

            int index = count;
            while (index > 0)
            {
                list.Add((ShakeObject)BinarySerializationHelper.Deserialize(br, typeof(ShakeObject)));
                index--;
            }
            return list;
        }

        private List<int> ReadIntegerList(BinaryReader br)
        {
            List<int> list = new List<int>();
            int count = br.ReadInt32();

            int index = count;
            while (index > 0)
            {
                list.Add(br.ReadInt32());
                index--;
            }
            return list;
        }


        private static WriteableBitmap ReadBitmap(BinaryReader br)
        {
            int wdt = br.ReadInt32();
            int hgt = br.ReadInt32();
            long len = br.ReadInt64();
            WriteableBitmap bmp = new WriteableBitmap(wdt, hgt);
            bmp = Decompress(br, len, bmp);

            return bmp;
        }
        private static void saveThumbnail(BinaryReader br, string name)
        {
            int wdt = br.ReadInt32();
            int hgt = br.ReadInt32();
            long len = br.ReadInt64();
            //WriteableBitmap bmp = new WriteableBitmap(wdt, hgt);
            //bmp = 
            writeThumbnail(br, len, name);


        }
        private Matrix ReadMatrix(BinaryReader br)
        {
            bool IsIdentity = br.ReadBoolean();
            double M11 = br.ReadDouble();
            double M12 = br.ReadDouble();
            double M21 = br.ReadDouble();
            double M22 = br.ReadDouble();
            double OffsetX = br.ReadDouble();
            double OffsetY = br.ReadDouble();
            Matrix matrix = new Matrix(M11, M12, M21, M22, OffsetX, OffsetY);

            return matrix;
        }
        public static WriteableBitmap Decompress(BinaryReader br, long len, WriteableBitmap wb)
        {

            // Create a file name for the JPEG file in isolated storage.
            String tempJPEG = "TempJPEG";

            // Create a virtual store and file stream. Check for duplicate tempJPEG files.
            var myStore = IsolatedStorageFile.GetUserStoreForApplication();
            if (myStore.FileExists(tempJPEG))
            {
                myStore.DeleteFile(tempJPEG);
            }

            IsolatedStorageFileStream myFileStream = myStore.CreateFile(tempJPEG);

            byte[] bytes = new byte[4096];
            int numBytesToRead = (int)len;
            int numBytesRead = 0;
            while (numBytesToRead > 0)
            {
                // Read may return anything from 0 to 10.
                int n = br.Read(bytes, 0, numBytesToRead < bytes.Length ? numBytesToRead : bytes.Length);
                myFileStream.Write(bytes, 0, n);
                // The end of the file is reached.
                if (n == 0)
                {
                    break;
                }
                numBytesRead += n;
                numBytesToRead -= n;
            }
            //for (int i = 0; i < len; i++)
            //{

            //    byte inputChar = br.ReadByte();
            //    myFileStream.WriteByte(inputChar);
            //}

            //byte[] bytes = new byte[len];
            //int numBytesToRead = (int)len;
            //int numBytesRead = 0;
            //while (numBytesToRead > 0)
            //{
            //    // Read may return anything from 0 to 10.
            //    int n = br.Read(bytes, numBytesRead, numBytesToRead < 1000 ? numBytesToRead : 1000);
            //    myFileStream.Write(bytes, numBytesRead, numBytesToRead);
            //    // The end of the file is reached.
            //    if (n == 0)
            //    {
            //        break;
            //    }
            //    numBytesRead += n;
            //    numBytesToRead -= n;
            //}


            myFileStream.Close();

            // Create a new stream from isolated storage, and save the JPEG file to the media library on Windows Phone.
            myFileStream = myStore.OpenFile(tempJPEG, FileMode.Open, FileAccess.Read);
            // Encode the WriteableBitmap object to a JPEG stream.

            wb.LoadJpeg(myFileStream);

            myFileStream.Close();


            return wb;
        }
        public static void writeThumbnail(BinaryReader br, long len, string shakername)
        {
           
            string name = Path.GetFileNameWithoutExtension(shakername) + ".jpg";
            // Create a file name for the JPEG file in isolated storage.
            // String tempJPEG = "TempJPEG";

            // Create a virtual store and file stream. Check for duplicate tempJPEG files.
            using (IsolatedStorageFile myStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!myStore.DirectoryExists(App.thumbPath))
                {
                    myStore.CreateDirectory(App.thumbPath);
                }

                if (myStore.FileExists(App.thumbPath + "\\" + name))
                {
                    myStore.DeleteFile(App.thumbPath + "\\" + name);
                }

                IsolatedStorageFileStream myFileStream = myStore.CreateFile(App.thumbPath +"\\"+ name);

                byte[] bytes = new byte[4096];
                int numBytesToRead = (int)len;
                int numBytesRead = 0;
                while (numBytesToRead > 0)
                {
                    // Read may return anything from 0 to 10.
                    int n = br.Read(bytes, 0, numBytesToRead < bytes.Length ? numBytesToRead : bytes.Length);
                    myFileStream.Write(bytes, 0, n);
                    // The end of the file is reached.
                    if (n == 0)
                    {
                        break;
                    }
                    numBytesRead += n;
                    numBytesToRead -= n;
                }

                myFileStream.Close();
            }
        }

        public static void GetThumbnail(BinaryReader br, string name)
        {
            //skip version info
            br.ReadInt32(); br.ReadInt32(); br.ReadInt32();

            saveThumbnail(br, name);
        }

        #endregion Read

    }
}
